package de.jmda.home.bl.usermgmt;

import static de.jmda.util.StringUtil.sb;
import static java.lang.System.lineSeparator;

import java.util.List;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import org.apache.commons.lang3.StringUtils;

import de.jmda.cbo.user.Encryptor;
import de.jmda.cbo.user.RegistrationRequest;
import de.jmda.cbo.user.User;
import de.jmda.home.bl.mail.Email;

@Stateless
@LocalBean
public class UserManagementBean implements UserManagementLocal
{
//	private final static Logger LOGGER =
//      Logger.getLogger(UserManagementBean.class);

	public final static String ADMIN = "admin";

	@PersistenceContext(unitName = "jmda.home.jpa")
	private EntityManager em;

	@Override
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
  public List<User> allUsers()
  {
	  return em.createNamedQuery(User.QUERY_ALL, User.class).getResultList();
  }

	@Override
	public String allUsersAsString(String prefix)
	{
		StringBuffer result = sb(prefix + lineSeparator());

		for (User user : allUsers())
		{
			result.append(user.asString() + lineSeparator());
		}
		
		return result.toString();
	}

	@Override
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
  public User findUserByUsername(String username)
  {
		TypedQuery<User> query =
				em.createNamedQuery(User.QUERY_BY_USERNAME, User.class);
		
		query.setParameter("username", username);

		try
		{
			return query.getSingleResult();
		}
		catch (NoResultException e)
		{
			return null;
		}
  }

	@Override
  public void createUser(User user)
  {
		em.persist(user);
  }

	@Override
	public void createAdminRegistrationRequest()
			throws AddressException, MessagingException
	{
		String email = "rowe.jmda@web.de";

		RegistrationRequest registrationRequest =
				new RegistrationRequest(ADMIN, email);

		try
		{
			em.persist(registrationRequest);
			em.flush();

			Email.send(
	        email,
	        "admin registration request",
	        "admin registration code: " +
	        		registrationRequest.getRegistrationCode() + lineSeparator() +
	        "Please use registration code as password next time you log in." +
	        		lineSeparator() +
	        "You can change your password any time once you are logged in.");
		}
		catch (Exception e)
		{
			// TODO can not execute a query after transaction was marked as rollback
			//      only
//			if (findRegistrationRequestByUserName(ADMIN) == null)
//			{
//				// if admin is not yet in db, something has gone really wrong
//				LOGGER.error("failure adding admin registration request", e);
//			}
		}
	}

	@Override
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public RegistrationRequest findRegistrationRequestByUserName(String username)
	{
		TypedQuery<RegistrationRequest> query =
				em.createNamedQuery(
						RegistrationRequest.QUERY_BY_USERNAME, RegistrationRequest.class);
		
		query.setParameter("username", username);

		try
		{
			return query.getSingleResult();
		}
		catch (NoResultException e)
		{
			return null;
		}
	}

	@Override
	public String findRegistrationRequestUsernameByUsernameRegistrationCode(
			String username, String registrationCode)
  {
		RegistrationRequest registrationRequest =
				findRegistrationRequestByUserName(username);

		if (registrationRequest != null)
		{
			if (registrationRequest.getRegistrationCode().equals(registrationCode))
			{
				return username;
			}
		}

		return null;
  }

	@Override
	public void changePassword(
			String username,
			String password,
			String passwordNew,
			String passwordNewConfirm)
  {
		if (StringUtils.isEmpty(username))
		{
			throw new IllegalStateException(
					"can not accept password changed without a logged in user");
		}

		User user = findUserByUsername(username);

		if (user == null)
		{
			RegistrationRequest registrationRequest =
					findRegistrationRequestByUserName(username);

			if (registrationRequest == null)
			{
				throw new IllegalStateException(
						"neither a user nor a registration request can be found for " +
						"username [" + username + "]");
			}

			// passwords from registration requests do not have to be encrypted
			validate(
					registrationRequest.getRegistrationCode(),
					password, passwordNew, passwordNewConfirm);
			
			user = new User(username, passwordNew, registrationRequest.getEmail());
			
			em.persist(user);
			em.remove(registrationRequest);
		}
		else
		{
			// passwords for registered users have to be encrypted
			validate(
					user.getPassword(), Encryptor.encryptPassword(password),
					passwordNew, passwordNewConfirm);

			user.setPassword(passwordNew);
			
			em.merge(user);
		}
  }

	@Override
	public void createRegistrationRequest(String username, String email)
  {
		if ((findRegistrationRequestByUserName(username) == null) &&
		    (findUserByUsername(username) == null))
		{
			RegistrationRequest registrationRequest =
					new RegistrationRequest(username, email);

			try
			{
				em.persist(registrationRequest);
				em.flush();

				Email.send(
		        email,
		        "registration request",
		        "registration code: " +
		        		registrationRequest.getRegistrationCode() + lineSeparator() +
		        "Please use registration code as password next time you log in." +
		        		lineSeparator() +
		        "You can change your password any time once you are logged in.");
			}
			catch (Exception e)
			{
				// TODO can not execute a query after transaction was marked as rollback
				//      only
//				if (findRegistrationRequestByUserName(ADMIN) == null)
//				{
//					// if admin is not yet in db, something has gone really wrong
//					LOGGER.error("failure adding admin registration request", e);
//				}
			}
		}
  }
	
	private void validate(
			String passwordFromStore,
			String passwordFromUser,
      String passwordNew,
      String passwordNewConfirm)
  {
		if (StringUtils.isEmpty(passwordFromStore))
		{
			throw new IllegalArgumentException("illegal empty password from store");
		}
		if (StringUtils.isEmpty(passwordFromUser))
		{
			throw new IllegalArgumentException("illegal empty password from user");
		}
		if (StringUtils.isEmpty(passwordNew))
		{
			throw new IllegalArgumentException("illegal empty new password");
		}
		if (StringUtils.isEmpty(passwordNewConfirm))
		{
			throw new IllegalArgumentException("illegal empty new password confirmation");
		}

		if (false == StringUtils.equals(passwordFromStore, passwordFromUser))
		{
			throw new IllegalArgumentException("passwords do not match");
		}
		if (false == StringUtils.equals(passwordNew, passwordNewConfirm))
		{
			throw new IllegalArgumentException("new and confirmed new passwords do not match");
		}
  }
}